<?php

namespace Xin\Robot\Monolog\Handler;

use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use Monolog\LogRecord;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\Console\Input\ArgvInput;
use Xin\Robot\RobotManager;

class AlarmHandler extends AbstractProcessingHandler
{

    /**
     * @var string
     */
    protected $config = [
        'robot' => 'danger',
        'error_key_prefix' => 'web_error_count:',
        'ignore_envs' => ['local'],
    ];

    /**
     * @var string|callable
     */
    protected static $appEnv = 'local';

    /**
     * @var bool|callable
     */
    protected static $runningInWeb = true;

    /**
     * @var CacheInterface|callable
     */
    protected static $cache;

    /**
     * RobotHandler constructor.
     *
     * @param array $config
     * @param int $level
     * @param bool $bubble
     */
    public function __construct(array $config = [], $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->config = array_replace_recursive($this->config, $config);
    }

    /**
     * @param callable|string $appEnv
     * @return void
     */
    public static function setAppEnv($appEnv)
    {
        self::$appEnv = $appEnv;
    }

    /**
     * @return string
     */
    protected static function getAppEnv(): string
    {
        if (is_callable(self::$appEnv)) {
            return call_user_func(self::$appEnv);
        }

        return self::$appEnv;
    }

    /**
     * 设置缓存器
     * @param callable|CacheInterface $cache
     * @return void
     */
    public static function setCache($cache)
    {
        self::$cache = $cache;
    }

    /**
     * 获取缓存器
     * @return CacheInterface
     */
    protected static function getCache(): CacheInterface
    {
        if (is_callable(self::$cache)) {
            return call_user_func(self::$cache);
        }

        return self::$cache;
    }

    /**
     * 设置是否运行在Web环境中
     * @param bool|callable $runningInWeb
     * @return void
     */
    public static function setRunningInWeb($runningInWeb)
    {
        self::$runningInWeb = $runningInWeb;
    }

    /**
     * @return bool
     */
    protected static function runningInWeb(): bool
    {
        if (is_callable(self::$runningInWeb)) {
            return call_user_func(self::$runningInWeb);
        }

        return self::$runningInWeb;
    }

    /**
     * @param array|LogRecord $record
     */
    protected function write(array|LogRecord $record): void
    {
        $env = self::getAppEnv();
        if (in_array($env, $this->config['ignore_envs'], true)) {
            return;
        }

        try {
            $currentUrl = $this->getCurrentUrl();
            $errorCount = $this->resolveErrorCount($currentUrl);
            if (!$this->allowSend($errorCount)) {
                return;
            }
        } catch (\Throwable $e) {
            return;
        }

        try {
            $message = $this->buildContents($record, $currentUrl, $errorCount);
            $robot = $record['context']['robot'] ?? null;
            $this->sendRobotMessage($message, $robot);
        } catch (\Throwable $e) {
            return;
        }
    }

    /**
     * 获取当前访问的URL
     * @return string
     */
    protected function getCurrentUrl(): string
    {
        if (!self::runningInWeb()) {
            $input = new ArgvInput();
            $url = $input->getFirstArgument() .
                " args:" . json_encode($input->getArguments()) .
                " options:" . json_encode($input->getOptions());
        } else {
            $request = request();
            $url = $request->fullUrl();
            $method = 'GET';
            if (method_exists($request, 'method')) {
                $method = $request->method();
            } elseif (method_exists($request, 'getMethod')) {
                $method = $request->getMethod();
            }
            $url = $method . " " . $url;
        }

        return $url;
    }

    /**
     * 解析错误次数
     * @param string $url
     * @return int
     * @throws InvalidArgumentException
     */
    protected function resolveErrorCount(string $url): int
    {
        $key = $this->makeErrorCacheKey($url);
        $cache = self::getCache();
        $count = $cache->get($key);

        if ($count === null) {
            $count = 1;
        } else {
            $count++;
        }

        $cache->set($key, $count, 10 * 60);

        return (int)$count;
    }

    /**
     * 生成缓存key
     * @param string $url
     * @return string
     */
    private function makeErrorCacheKey(string $url): string
    {
        $url = preg_replace('/\&timestamp=\d{0,10}/', '', $url);
        return $this->config['error_key_prefix'] . md5($url);
    }

    /**
     * 自动降级发送
     *
     * @param int $count
     * @return bool
     * @throws InvalidArgumentException
     */
    protected function allowSend(int $count = 0): bool
    {
        if ($count === 1) {
            return true;
        }

        $levels = [10, 100, 1000, 10000];
        $maxLevel = $levels[0];
        foreach ($levels as $level) {
            if ($count > $level) {
                $maxLevel = $level;
            } else {
                break;
            }
        }

        return $count % $maxLevel === 0;
    }

    /**
     * @param array|LogRecord $record
     * @param string $currentUrl
     * @param int $errorCount
     * @return string
     */
    protected function buildContents(array|LogRecord $record, string $currentUrl, int $errorCount): string
    {
        $title = $record['level_name'];
        $message = $record['formatted'];
        $message = substr($message, 0, 1024);
        $ip = gethostbyname(gethostname());
        $env = self::getAppEnv();

        return <<<MARKDOWN
<font color="warning">**{$title}({$env}:{$ip}:10 分钟出现 {$errorCount} 次)**</font><br>
<font color="info">{$currentUrl}</font><br>
<font color="comment">{$message}</font>
MARKDOWN;
    }

    /**
     * 机器人发送告警消息
     * @param string $contents
     * @param mixed $useRobot
     * @return void
     */
    protected function sendRobotMessage(string $contents, string $useRobot = null): void
    {
        $useRobot = $useRobot ?: $this->robot();
        $useRobot = $useRobot === true ? null : $useRobot;
        try {
            /** @var RobotManager $factory */
            $factory = app('robot');
            $result = $factory->robot($useRobot)->sendMarkdownMessage($contents);
        } catch (\Throwable $e) {
        }

    }

    /**
     * 获取机器人Key
     *
     * @return string|null
     */
    protected function robot(): ?string
    {
        if (isset($this->config['robot']) && $this->config['robot']) {
            return $this->config['robot'];
        }

        return null;
    }

}
